package com.gmrz.uaf.protocol.v1.json;

import java.io.ByteArrayInputStream;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.List;

import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.crypto.CryptoEngine;
import com.google.inject.Inject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.google.gson.InstanceCreator;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.TLVTag;
import com.gmrz.uaf.common.TLVType2;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.ASMRegResponse;
import com.gmrz.uaf.protocol.v1.schema.KeyRegistrationData;
import com.gmrz.util.Convert;

public class ASMRegResponseAdapter implements InstanceCreator<ASMRegResponse>,
		JsonSerializer<ASMRegResponse>, JsonDeserializer<ASMRegResponse> {
	private static final Logger LOG = LogManager
			.getLogger(ASMRegResponseAdapter.class);
	@Inject
	private static CryptoEngine cryptoEngine = null;

	public ASMRegResponse createInstance(Type type) {
		return new ASMRegResponse();
	}

	public JsonElement serialize(ASMRegResponse src, Type typeofSrc,
			JsonSerializationContext context) {
		try {
			byte[] krdBytes = src.getKRD().getReceivedKRDBytes();

			TLVType2 signTag = new TLVType2() {
			};
			byte[] signatureBytes = src.getSignature() == null ? new byte[0]
					: src.getSignature();
			signTag.addTLVSegment(
					Constants.UAFV1AssertionTag.TAG_SIGNATURE.getTagValue(),
					signatureBytes);
			byte[] signBytes = signTag.getTLVBytes();

			List<Certificate> certs = src.getAttestationCerts();
			List<TLVType2> certTags = new ArrayList<TLVType2>();
			if (certs != null) {
				for (Certificate cert : certs) {
					TLVType2 certTag = new TLVType2() {
					};
					byte[] encodedCertBytes = cert == null ? new byte[0] : cert
							.getEncoded();
					certTag.addTLVSegment(
							Constants.UAFV1AssertionTag.TAG_ATTESTATION_CERT
									.getTagValue(), encodedCertBytes);
					certTags.add(certTag);
				}
			}
			int len = 0;
			for (TLVType2 t : certTags) {
				len += t.getTLVBytes().length;
			}

			int attestationLength = signBytes.length + len;
			ByteBuffer attestationBuffer = ByteBuffer.allocate(
					attestationLength).order(ByteOrder.LITTLE_ENDIAN);
			attestationBuffer.put(signBytes);
			for (TLVType2 t : certTags) {
				byte[] certBytes = t.getTLVBytes();
				attestationBuffer.put(certBytes);
			}

			TLVType2 attestationTag = new TLVType2() {
			};
			attestationTag.addTLVSegment((short) src.getAttestationType()
					.getTag(), attestationBuffer.array());

			TLVType2 krdTag = new TLVType2() {
			};
			krdTag.addTLVSegment(
					Constants.UAFV1AssertionTag.TAG_UAFV1_KRD.getTagValue(),
					krdBytes);

			int totalLength = krdTag.getTLVBytes().length
					+ attestationTag.getTLVBytes().length;
			ByteBuffer regAssertionBuf = ByteBuffer.allocate(totalLength)
					.order(ByteOrder.LITTLE_ENDIAN);
			regAssertionBuf.put(krdTag.getTLVBytes()).put(
					attestationTag.getTLVBytes());

			TLVType2 regAssertionTag = new TLVType2() {
			};
			regAssertionTag.addTLVSegment(
					Constants.UAFV1AssertionTag.TAG_UAFV1_REG_ASSERTION
							.getTagValue(), regAssertionBuf.array());

			String encoded = Convert.toBase64(regAssertionTag.getTLVBytes());
			return context.serialize(encoded);
		} catch (Exception ex) {
			LOG.error("Error creating the Registration TLV data, ", ex);
			throw new JsonParseException(
					UAFErrorCode.PROTOCOL_TLV_PARSING_FAILED.toString(), ex);
		}
	}

	public ASMRegResponse deserialize(JsonElement json, Type typeofT,
			JsonDeserializationContext context) throws JsonParseException {
		try {
			String encoded = json.getAsString();
            // 使用base64 解码成字节数组
			byte[] rawResponse = Convert.fromBase64(encoded);

			/**
			 * 先将一个字节数组包装到缓冲区中，并设置缓冲区中的字节序；
			 *
			 * 字节序：分为 大字节序（BIG_ENDIAN）、小字节序（LITTLE_ENDIA）
			 * BIG_ENDIAN ：大端字节序是高字节数据存放在低地址处，低字节数据存放在高地址处。
			 * LITTLE_ENDIA ：小端字节序指低字节数据存放在内存低地址处，高字节数据存放在内存高地址处；
			 */
			ByteBuffer buffer = ByteBuffer.wrap(rawResponse).order(
					ByteOrder.LITTLE_ENDIAN);
			TLVTag assertionTag = new TLVTag(TLVTag.readTag(buffer));
			byte[] assertionBytes = assertionTag.parseByteArray(buffer);

			ByteBuffer resBuffer = ByteBuffer.wrap(assertionBytes).order(
					ByteOrder.LITTLE_ENDIAN);

			TLVTag krdTag = new TLVTag(TLVTag.readTag(resBuffer));
			byte[] krdBytes = krdTag.parseByteArray(resBuffer);

			TLVTag attestationTag = new TLVTag(TLVTag.readTag(resBuffer));
			byte[] attestationBytes = attestationTag.parseByteArray(resBuffer);
			Constants.AttestationType aType = Constants.AttestationType
					.forValue(attestationTag.getValue());

			ByteBuffer attestationBuf = ByteBuffer.wrap(attestationBytes)
					.order(ByteOrder.LITTLE_ENDIAN);

			TLVTag signTag = new TLVTag(TLVTag.readTag(attestationBuf));
			byte[] signatureBytes = signTag.parseByteArray(attestationBuf);

			List<Certificate> attestationCerts = new ArrayList<Certificate>();
			while (attestationBuf.hasRemaining()) {
				TLVTag certTag = new TLVTag(TLVTag.readTag(attestationBuf));
				byte[] certBytes = certTag.parseByteArray(attestationBuf);
				if ((certBytes != null) && (certBytes.length > 0)) {
//					CertificateFactory cf = CertificateFactory
//							.getInstance("X.509");
//					Certificate cert = cf
//							.generateCertificate(new ByteArrayInputStream(
//									certBytes));
					if(null == cryptoEngine){
						cryptoEngine = GuiceUtil.getProcessorInjector().getInstance(CryptoEngine.class);
					}
					Certificate cert = cryptoEngine.getAttestationCertHandler().decode(certBytes);
					attestationCerts.add(cert);
				}
			}
			ASMRegResponse asmRegResponse = new ASMRegResponse();

			if ((krdBytes == null) || (krdBytes.length <= 0)) {
				throw new JsonParseException(
						"Key registration data not found in registration response");
			}
			if ((signatureBytes == null) || (signatureBytes.length <= 0)) {
				throw new JsonParseException(
						"Signature data not found in registration response");
			}

			asmRegResponse.setAttestationType(aType);

			KeyRegistrationData krd = new KeyRegistrationData();
			krd.setReceivedKRDBytes(krdBytes);
			krd.parseTLVBytes(krdBytes);
			asmRegResponse.setKRD(krd);

			asmRegResponse.setSignature(signatureBytes);

			asmRegResponse.setAttestationCerts(attestationCerts);

			return asmRegResponse;
		} catch (Exception ex) {
			LOG.error("Error parsing the Registration TLV data, ", ex);
			throw new JsonParseException(
					UAFErrorCode.PROTOCOL_TLV_PARSING_FAILED.toString(), ex);
		}
	}
}
